/*
 * ClientHandler.cpp
 */

#include "ClientHandler.h"
#include <unistd.h>

ClientHandler::ClientHandler(const ServerConfig& config, LogFile& logFile):
config(config), logFile(logFile), currentConn(0) {}

ClientHandler::~ClientHandler() {
	killEverything();
}

void ClientHandler::run() {
	while(!isDead()) {
		// Espero a que hayas conexiones activas para seguir
		currentConn_Cv.lock();
		if(currentConn == 0)
			currentConn_Cv.wait();
		currentConn_Cv.unlock();
		// Libero finalizados y mato clientes excedidos en tiempo
		freeProcessedAndKill();
		// Agrego los nuevos clientes a la lista de monitoreo
		mergeClientMessages();
	}
	killEverything();
}

void ClientHandler::addNewClient(Socket* sock) {
	// Verifico que el cliente no supere su limite max de conexiones
	std::string clientId = sock->getIP();
	if (!isDead() && checkInClient(clientId)) {
		// Lanzo al cliente
		Lock lock(newMessages_Mutex);
		ClientMessage* msg = new ClientMessage(sock, config, logFile, clientId);
		newMessages.push_back(msg);
		msg->start();
	}
	else {
		// Descarto al cliente
		sock->close();
		delete sock;
		logFile.addError("El cliente " + clientId + " superó su límite "
				"de conexiones activas.");
	}
}

void ClientHandler::freeProcessedAndKill() {
	Lock lock(messages_Mutex);
	tMsgIt it = messages.begin();
	while(it != messages.end() && !isDead()) {
		// Si el cliente finalizo, libero recursos
		if ((*it)->finished()) {
			checkOutClient((*it)->getId());
			(*it)->join();
			delete *it;
			it = messages.erase(it);
		}
		else if ((*it)->getElapsedTime() >= config.getTimeout()
				&& !(*it)->isDead() && !(*it)->finished()) {
			// Si se paso del timeout, lo mato
			logFile.addError("El cliente " + (*it)->getId() +
					" superó su timeout");
			(*it)->die();
			++it;
		}
		else
			++it;
	}
}

bool ClientHandler::checkInClient(const std::string& clientId) {
	Lock lockMsgCount(messageCount_Mutex);
	Lock lockCurrConnCv(currentConn_Cv);
	bool thereIsRoom = false;
	// Verifico que no se supere la maxima cantidad de conexiones simultaneas
	if (currentConn < config.getMaxSimulConn()) {
		tMsgCountIt it = messageCount.find(clientId);
		if (it == messageCount.end()) {
			messageCount[clientId] = 1;
			if(currentConn++ == 0)
				currentConn_Cv.signal();  // Señalizo wait del run()
			thereIsRoom = true;
		}
		// Verifico que no supere la maxima cantidad de conexiones por cliente
		else if (it->second < config.getMaxClientConn()) {
			++(it->second);
			if(currentConn++ == 0)
				currentConn_Cv.signal();  // Señalizo wait del run()
			thereIsRoom = true;
		}
	}
	return thereIsRoom;
}

void ClientHandler::waitForRoom() {
	if (config.getMaxSimulConn() > 0 && !isDead()) {
		Lock lock(currentConn_Cv);
		if (currentConn >= config.getMaxSimulConn() && !isDead())
			currentConn_Cv.wait();  // Espero señalización de clientLeaves()
	}
}

void ClientHandler::die() {
	Thread::die();
	// Señalizo todos los puntos de espera
	Lock lock(currentConn_Cv);
	currentConn_Cv.broadcast();
}

void ClientHandler::checkOutClient(const std::string& clientId) {
	// Busco al cliente en el mapa y descuento su contador de conexiones
	Lock lock(messageCount_Mutex);
	tMsgCountIt it = messageCount.find(clientId);
	if (it != messageCount.end()) {
		if (it->second == 1)
			messageCount.erase(it);
		else
			--it->second;
	} else
		logFile.addError("El cliente " + clientId + " se esfumo.");
	// Si me encuentro en estado de sobrecarga, señalizo al wait
	Lock lockCv(currentConn_Cv);
	if(currentConn-- >= config.getMaxSimulConn())
		currentConn_Cv.signal();  // Señalizo a waitForRoom()
}

void ClientHandler::mergeClientMessages() {
	// Si no debo morir, uno las listas de clientes
	if(!isDead()) {
		Lock lockMsg(messages_Mutex);
		Lock lockNewMsg(newMessages_Mutex);
		messages.splice(messages.begin(), newMessages);
	}
}

void ClientHandler::killEverything() {
	// Mato a todos los clientes registrados
	Lock lockMsg(messages_Mutex);
	tMsgIt it;
	for(it = messages.begin(); it != messages.end(); ++it) {
		if (!(*it)->isDead())
			(*it)->die();
		(*it)->join();
		delete *it;
	}
	messages.clear();
	Lock lockNewMsg(newMessages_Mutex);
	for(it = newMessages.begin(); it != newMessages.end(); ++it) {
		if (!(*it)->isDead())
			(*it)->die();
		(*it)->join();
		delete *it;
	}
	newMessages.clear();
	Lock lockCv(currentConn_Cv);
	currentConn = 0;
}
